﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TurtlePoker
{
    enum GameMode
    {
        Init, // player's card number < 5
        Start, // first move
        Normal, // card number is less than 5
        Decide,
        Final
    }

    enum NormalChoice
    {
        DealAndPut,
        ReplaceAll
    }

    enum DecideChoice
    {
        Replace,
        GiveUp,
    }

    enum FinalResult
    {
        Suppress,
        NotSuppress,
    }


    class Game
    {
        private Dictionary<Card, double> s_code_to_score = new Dictionary<Card, double> ();
        private CardManager m_cm;
        private List<Player> m_players;
        private int m_current_player_index = 0;
        private List<double> m_scores;
        private GameMode m_mode;
        private int m_card_ok = 0;

        public const int CARD_HOLD_LIMIT = 5;
        public const int CARD_REP_LIMIT = 5;

        protected void init_dict()
        {
            for (int i = 0; i < 52; i++)
            {
                if (i % 13 == 0)
                {
                    s_code_to_score.Add(new Card(i), 11.0); // A -> 11
                }
                else
                {
                    s_code_to_score.Add(new Card(i), Math.Min(10, i % 13 + 1));
                }
            }
            // jokers
            s_code_to_score.Add(new Card(52), 10);
            s_code_to_score.Add(new Card(53), 10);

        }

        private void init()
        {
            this.init_dict();
            this.m_cm = new CardManager();
            this.m_mode = GameMode.Init;
            this.m_players = new List<Player>();   
        }

        public Game()
        {
            init();
        }

        public Game(List<Player> pl)
        {
            init();
            this.m_players = pl;
        }

        public void run()
        {
            // running until cards runs out
            while (true)
            {
                if (this.m_cm.empty() == true)
                {
                    this.m_mode = GameMode.Final;
                }
                if (this.next())
                {
                    print_scores();
                    break;
                }
            }
        }

        private bool next()
        {        
            switch (this.m_mode)
            {
                case GameMode.Init:
                    {
                        int current_plyr_index = this.m_current_player_index % this.m_players.Count;
                        this.m_current_player_index++;
                        Player p = this.m_players[current_plyr_index];
                        int card_num = p.init_op(this.m_cm);
                        if (card_num == CARD_HOLD_LIMIT)
                        {
                            m_card_ok++;
                            if (this.m_card_ok == this.m_players.Count)
                                this.m_mode = GameMode.Start;
                        }
                    }
                    return false;
                case GameMode.Start:
                    {
                        int current_plyr_index = this.m_current_player_index % this.m_players.Count;
                        Player p = this.m_players[current_plyr_index];
                        this.m_current_player_index++;

                        if (p.enter_final(this.m_cm))
                        {
                            this.m_mode = GameMode.Final;
                            return false;
                        }

                        p.start_op(this.m_cm);
                        this.m_mode = GameMode.Normal;

                        return false;
                    }
                case GameMode.Normal:
                    {
                        print_rep_cards();
                        // Update current player index
                        int current_plyr_index = this.m_current_player_index % this.m_players.Count;
                        this.m_current_player_index++;

                        Player p = this.m_players[current_plyr_index];
                        
                        if (p.enter_final(this.m_cm))
                        {
                            this.m_mode = GameMode.Final;
                            return false;
                        }
                        p.normal_op(this.m_cm);
                        if (m_cm.get_card_rep().Count == CARD_REP_LIMIT)
                            this.m_mode = GameMode.Decide;
                    }
                    return false;
                case GameMode.Decide:
                    {
                        print_rep_cards();
                        // Update current player index
                        int current_plyr_index = this.m_current_player_index % this.m_players.Count;
                        this.m_current_player_index++;

                        Player p = this.m_players[current_plyr_index];
                        if (p.enter_final(this.m_cm))
                        {
                            this.m_mode = GameMode.Final;
                            return false;
                        }

                        DecideChoice dc = p.decide_op(this.m_cm);
                        if (dc == DecideChoice.GiveUp)
                        {
                            this.m_mode = GameMode.Start;
                            // clear replaceable card pile
                            this.m_cm.set_card_rep(new List<Card>());
                            return false;
                        }
                    }
                    return false;
                case GameMode.Final:
                    {
                        bool[] suppress = get_suppress_result();

                        // calculate score
                        double[] raw_scores = get_raw_scores();

                        this.m_scores = new List<double>();
                        for (int i = 0; i < this.m_players.Count; i++)
                        {
                            m_scores.Add(0);
                        }

                        for (int i = 0; i < this.m_players.Count; i++)
                        {
                            if (suppress[i])
                            {
                                int last_index = (i - 1 + this.m_players.Count) % this.m_players.Count;
                                // if this player wins
                                if (raw_scores[i] > raw_scores[last_index])
                                {
                                    this.m_scores[i] = raw_scores[i] + raw_scores[last_index];
                                    this.m_scores[last_index] = 0;
                                }
                                else
                                {
                                    this.m_scores[i] = 0;
                                    this.m_scores[last_index] = raw_scores[i] + raw_scores[last_index];
                                }
                            }
                            else
                            {
                                this.m_scores[i] = raw_scores[i];
                            }
                        }
                    }
                    return true;
                default:
                    return false;
            }
        }

        private bool[] get_suppress_result()
        {
            bool[] suppress = new bool[this.m_players.Count];

            for (int i = 1; i < this.m_players.Count; i++)
            {
                // Update current player index
                this.m_current_player_index = (this.m_current_player_index + i) % this.m_players.Count;
                Player p = this.m_players[this.m_current_player_index];
                int last_player_index = (this.m_current_player_index - 1 + this.m_players.Count) % this.m_players.Count;
                Player last_p = this.m_players[last_player_index];

                FinalResult dc = p.final_op(this.m_cm, last_p);
                if (dc == FinalResult.Suppress)
                {
                    suppress[m_current_player_index] = true;
                }
                else
                {
                    suppress[m_current_player_index] = false;
                }
            }

            return suppress;
        }

        private double[] get_raw_scores()
        {
            // calculate score
            double[] raw_scores = new double[this.m_players.Count];

            for (int i = 0; i < this.m_players.Count; i++)
            {
                Player p = this.m_players[i];
                raw_scores[i] = this.get_score(p.get_cards());
            }
            return raw_scores;
        }

        private double get_score(List<Card> cards)
        {
            if (same_type(cards) == false)
                return 0.0;

            double score = 0.0;
            foreach (Card c in cards)
            {
                score += s_code_to_score[c];
            }
            return score;
        }

        private bool same_type(List<Card> cards)
        {
            List<Card> no_joker_cards = new List<Card>();
            for (int i = 0; i < cards.Count; i++)
            {
                // first remove all the joker card
                if (cards[i].m_type != CardType.SmallJoker && cards[i].m_type != CardType.BigJoker)
                {
                    no_joker_cards.Add(cards[i]);
                }
            }
            for (int i = 1; i < no_joker_cards.Count; i++)
            {
                if (no_joker_cards[0] != no_joker_cards[i])
                    return false;
            }
            return true;
        }

        private void print_rep_cards()
        {
            Console.Write("Replaceable cards:");
            foreach (Card c in this.m_cm.get_card_rep())
            {
                Console.Write(c.ToString());
            }
            Console.Write("\n");
        }

        private void print_scores()
        {
            for (int i = 0; i < this.m_scores.Count; i++)
            {
                Console.WriteLine("Player {0} {1}: {2} points.", i, this.m_players[i].ToString(), this.m_scores[i]);
            }
        }
    }
}
